import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";

/* eslint-disable no-new-func */
import esTokenizer from "./es-tokenizer";
import tplTokenizer from "./tpl-tokenizer";
/** 传递给模板的数据引用 */

var DATA = "$data";
/** 外部导入的所有全局变量引用 */

var IMPORTS = "$imports";
/**  $imports.$escape */

var ESCAPE = "$escape";
/**  $imports.$each */

var EACH = "$each";
/** 文本输出函数 */

var PRINT = "print";
/** 包含子模板函数 */

var INCLUDE = "include";
/** 继承布局模板函数 */

var EXTEND = "extend";
/** “模板块”读写函数 */

var BLOCK = "block";
/** 字符串拼接变量 */

var OUT = "$$out";
/** 运行时逐行调试记录变量 [line, start, source] */

var LINE = "$$line";
/** 所有“模板块”变量 */

var BLOCKS = "$$blocks";
/** 截取模版输出“流”的函数 */

var SLICE = "$$slice";
/** 继承的布局模板的文件地址变量 */

var FROM = "$$from";
/** 编译设置变量 */

var OPTIONS = "$$options";

var has = function has(object, key) {
  return Object.hasOwnProperty.call(object, key);
};

var stringify = JSON.stringify;

var Compiler = /*#__PURE__*/function () {
  /**
   * 模板编译器
   * @param   {Object}    options
   */
  function Compiler(options) {
    var _this$internal,
        _this$dependencies,
        _this = this;

    _classCallCheck(this, Compiler);

    var source = options.source; // const { minimize } = options;
    // const { htmlMinifier } = options;
    // 编译选项

    this.options = options; // 所有语句堆栈

    this.stacks = []; // 运行时注入的上下文

    this.context = []; // 模板语句编译后的代码

    this.scripts = []; // context map

    this.CONTEXT_MAP = {}; // 忽略的变量名单

    this.ignore = [DATA, IMPORTS, OPTIONS].concat(_toConsumableArray(options.ignore)); // 按需编译到模板渲染函数的内置变量

    this.internal = (_this$internal = {}, _defineProperty(_this$internal, OUT, "''"), _defineProperty(_this$internal, LINE, "[0,0]"), _defineProperty(_this$internal, BLOCKS, "arguments[1]||{}"), _defineProperty(_this$internal, FROM, "null"), _defineProperty(_this$internal, PRINT, "function(){var s=''.concat.apply('',arguments);".concat(OUT, "+=s;return s}")), _defineProperty(_this$internal, INCLUDE, "function(src,data){var s=".concat(OPTIONS, ".include(src,data||").concat(DATA, ",arguments[2]||").concat(BLOCKS, ",").concat(OPTIONS, ");").concat(OUT, "+=s;return s}")), _defineProperty(_this$internal, EXTEND, "function(from){".concat(FROM, "=from}")), _defineProperty(_this$internal, SLICE, "function(c,p,s){p=".concat(OUT, ";").concat(OUT, "='';c();s=").concat(OUT, ";").concat(OUT, "=p+s;return s}")), _defineProperty(_this$internal, BLOCK, "function(){var a=arguments,s;if(typeof a[0]==='function'){return ".concat(SLICE, "(a[0])}else if(").concat(FROM, "){if(!").concat(BLOCKS, "[a[0]]){").concat(BLOCKS, "[a[0]]=").concat(SLICE, "(a[1])}else{").concat(OUT, "+=").concat(BLOCKS, "[a[0]]}}else{s=").concat(BLOCKS, "[a[0]];if(typeof s==='string'){").concat(OUT, "+=s}else{s=").concat(SLICE, "(a[1])}return s}}")), _this$internal); // 内置函数依赖关系声明

    this.dependencies = (_this$dependencies = {}, _defineProperty(_this$dependencies, PRINT, [OUT]), _defineProperty(_this$dependencies, INCLUDE, [OUT, OPTIONS, DATA, BLOCKS]), _defineProperty(_this$dependencies, EXTEND, [FROM, INCLUDE]), _defineProperty(_this$dependencies, BLOCK, [SLICE, FROM, OUT, BLOCKS]), _this$dependencies);
    this.importContext(OUT);

    if (options.compileDebug) {
      this.importContext(LINE);
    } // if (minimize) {
    //     try {
    //         source = htmlMinifier(source, options);
    //         // eslint-disable-next-line no-empty
    //     } catch (error) { }
    // }


    this.source = source;
    this.getTplTokens(source, options.rules, this).forEach(function (tokens) {
      if (tokens.type === tplTokenizer.TYPE_STRING) {
        _this.parseString(tokens);
      } else {
        _this.parseExpression(tokens);
      }
    });
  }
  /**
   * 将模板代码转换成 tplToken 数组
   * @param   {string} source
   * @return  {Object[]}
   */


  _createClass(Compiler, [{
    key: "getTplTokens",
    value: function getTplTokens() {
      return tplTokenizer.apply(void 0, arguments);
    }
    /**
     * 将模板表达式转换成 esToken 数组
     * @param   {string} source
     * @return  {Object[]}
     */

  }, {
    key: "getEsTokens",
    value: function getEsTokens(source) {
      return esTokenizer(source);
    }
    /**
     * 获取变量列表
     * @param {Object[]} esTokens
     * @return {string[]}
     */

  }, {
    key: "getVariables",
    value: function getVariables(esTokens) {
      var ignore = false;
      return esTokens.filter(function (esToken) {
        return esToken.type !== "whitespace" && esToken.type !== "comment";
      }).filter(function (esToken) {
        if (esToken.type === "name" && !ignore) {
          return true;
        }

        ignore = esToken.type === "punctuator" && esToken.value === ".";
        return false;
      }).map(function (tooken) {
        return tooken.value;
      });
    }
    /**
     * 导入模板上下文
     * @param {string} name
     */

  }, {
    key: "importContext",
    value: function importContext(name) {
      var _this2 = this;

      var value = "";
      var internal = this.internal,
          dependencies = this.dependencies,
          ignore = this.ignore,
          context = this.context,
          options = this.options;
      var imports = options.imports;
      var contextMap = this.CONTEXT_MAP;

      if (!has(contextMap, name) && ignore.indexOf(name) === -1) {
        if (has(internal, name)) {
          value = internal[name];

          if (has(dependencies, name)) {
            dependencies[name].forEach(function (i) {
              return _this2.importContext(i);
            });
          } // imports 继承了 Global，但是继承的属性不分配到顶级变量中，避免占用了模板内部的变量名称

        } else if (name === ESCAPE || name === EACH || has(imports, name)) {
          value = "".concat(IMPORTS, ".").concat(name);
        } else {
          value = "".concat(DATA, ".").concat(name);
        }

        contextMap[name] = value;
        context.push({
          name: name,
          value: value
        });
      }
    }
    /**
     * 解析字符串（HTML）直接输出语句
     * @param {Object} tplToken
     */

  }, {
    key: "parseString",
    value: function parseString(tplToken) {
      var source = tplToken.value;

      if (!source) {
        return;
      }

      var code = "".concat(OUT, "+=").concat(stringify(source));
      this.scripts.push({
        source: source,
        tplToken: tplToken,
        code: code
      });
    }
    /**
     * 解析逻辑表达式语句
     * @param {Object} tplToken
     */

  }, {
    key: "parseExpression",
    value: function parseExpression(tplToken) {
      var _this3 = this;

      var source = tplToken.value;
      var script = tplToken.script;
      var output = script.output;
      var escape = this.options.escape;
      var code = script.code;

      if (output) {
        if (escape === false || output === tplTokenizer.TYPE_RAW) {
          code = "".concat(OUT, "+=").concat(script.code);
        } else {
          code = "".concat(OUT, "+=").concat(ESCAPE, "(").concat(script.code, ")");
        }
      }

      var esToken = this.getEsTokens(code);
      this.getVariables(esToken).forEach(function (name) {
        return _this3.importContext(name);
      });
      this.scripts.push({
        source: source,
        tplToken: tplToken,
        code: code
      });
    }
    /**
     * 检查解析后的模板语句是否存在语法错误
     * @param  {string} script
     * @return {boolean}
     */

  }, {
    key: "checkExpression",
    value: function checkExpression(script) {
      // 没有闭合的块级模板语句规则
      // 基于正则规则来补全语法不能保证 100% 准确，
      // 但是在绝大多数情况下足以满足辅助开发调试的需要
      var rules = [// <% } %>
      // <% }else{ %>
      // <% }else if(a){ %>
      [/^\s*}[\w\W]*?{?[\s;]*$/, ''], // <% fn(c,function(a,b){ %>
      // <% fn(c, a=>{ %>
      // <% fn(c,(a,b)=>{ %>
      [/(^[\w\W]*?\([\w\W]*?(?:=>|\([\w\W]*?\))\s*{[\s;]*$)/, '$1})'], // <% if(a){ %>
      // <% for(var i in d){ %>
      [/(^[\w\W]*?\([\w\W]*?\)\s*{[\s;]*$)/, '$1}']];
      var index = 0;

      while (index < rules.length) {
        if (rules[index][0].test(script)) {
          var _script;

          // eslint-disable-next-line no-param-reassign
          script = (_script = script).replace.apply(_script, _toConsumableArray(rules[index]));
          break;
        }

        index++;
      }

      try {
        // eslint-disable-next-line no-new
        new Function(script);
        return true;
      } catch (e) {
        return false;
      }
    }
    /**
     * 编译
     * @return  {function}
     */

  }, {
    key: "build",
    value: function build() {
      var options = this.options,
          context = this.context,
          scripts = this.scripts,
          stacks = this.stacks,
          source = this.source;
      var filename = options.filename,
          imports = options.imports;
      var mappings = [];
      var extendMode = has(this.CONTEXT_MAP, EXTEND);
      var offsetLine = 0; // Create SourceMap: mapping

      var mapping = function mapping(code, _ref) {
        var line = _ref.line,
            start = _ref.start;
        var node = {
          generated: {
            line: stacks.length + offsetLine + 1,
            column: 1
          },
          original: {
            line: line + 1,
            column: start + 1
          }
        };
        offsetLine += code.split(/\n/).length - 1;
        return node;
      }; // Trim code


      var trim = function trim(code) {
        return code.replace(/^[\t ]+|[\t ]$/g, '');
      };

      stacks.push("function(".concat(DATA, "){"));
      stacks.push("'use strict'");
      stacks.push("".concat(DATA, "=").concat(DATA, "||{}"));
      stacks.push("var ".concat(context.map(function (_ref2) {
        var name = _ref2.name,
            value = _ref2.value;
        return "".concat(name, "=").concat(value);
      }).join(",")));

      if (options.compileDebug) {
        stacks.push("try{");
        scripts.forEach(function (script) {
          if (script.tplToken.type === tplTokenizer.TYPE_EXPRESSION) {
            stacks.push("".concat(LINE, "=[").concat([script.tplToken.line, script.tplToken.start].join(','), "]"));
          }

          mappings.push(mapping(script.code, script.tplToken));
          stacks.push(trim(script.code));
        });
        stacks.push("}catch(error){");
        stacks.push("throw {".concat(["name:'RuntimeError'", "path:".concat(stringify(filename)), "message:error.message", "line:".concat(LINE, "[0]+1"), "column:".concat(LINE, "[1]+1"), "source:".concat(stringify(source)), "stack:error.stack"].join(","), "}"));
        stacks.push("}");
      } else {
        stacks.push("try{");
        scripts.forEach(function (script) {
          mappings.push(mapping(script.code, script.tplToken));
          stacks.push(trim(script.code));
        });
        stacks.push("}catch(error){");
        stacks.push("return '[Config Error]';");
        stacks.push("}");
      }

      if (extendMode) {
        stacks.push("".concat(OUT, "=''"));
        stacks.push("".concat(INCLUDE, "(").concat(FROM, ",").concat(DATA, ",").concat(BLOCKS, ")"));
      }

      stacks.push("return ".concat(OUT));
      stacks.push("}");
      var renderCode = stacks.join("\n");

      try {
        var result = new Function(IMPORTS, OPTIONS, "return ".concat(renderCode))(imports, options);
        result.mappings = mappings;
        result.sourcesContent = [source];
        return result;
      } catch (error) {
        var index = 0;
        var line = 0;
        var start = 0;
        var generated;

        while (index < scripts.length) {
          var current = scripts[index];

          if (!this.checkExpression(current.code)) {
            // eslint-disable-next-line prefer-destructuring
            line = current.tplToken.line; // eslint-disable-next-line prefer-destructuring

            start = current.tplToken.start;
            generated = current.code;
            break;
          }

          index++;
        } // eslint-disable-next-line no-throw-literal


        throw {
          name: "CompileError",
          path: filename,
          message: error.message,
          line: line + 1,
          column: start + 1,
          source: source,
          generated: generated,
          stack: error.stack
        };
      }
    }
  }]);

  return Compiler;
}();
/**
 * 模板内置常量
 */


Compiler.CONSTS = {
  DATA: DATA,
  IMPORTS: IMPORTS,
  PRINT: PRINT,
  INCLUDE: INCLUDE,
  EXTEND: EXTEND,
  BLOCK: BLOCK,
  OPTIONS: OPTIONS,
  OUT: OUT,
  LINE: LINE,
  BLOCKS: BLOCKS,
  SLICE: SLICE,
  FROM: FROM,
  ESCAPE: ESCAPE,
  EACH: EACH
};
export default Compiler;